// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/appcache/appcache.h"

#include <stddef.h>

#include <algorithm>

#include "base/logging.h"
#include "base/stl_util.h"
#include "content/browser/appcache/appcache_executable_handler.h"
#include "content/browser/appcache/appcache_group.h"
#include "content/browser/appcache/appcache_host.h"
#include "content/browser/appcache/appcache_storage.h"
#include "content/common/appcache_interfaces.h"

namespace content {

AppCache::AppCache(AppCacheStorage* storage, int64_t cache_id)
    : cache_id_(cache_id)
    , owning_group_(nullptr)
    , online_whitelist_all_(false)
    , is_complete_(false)
    , cache_size_(0)
    , storage_(storage)
{
    storage_->working_set()->AddCache(this);
}

AppCache::~AppCache()
{
    DCHECK(associated_hosts_.empty());
    if (owning_group_.get()) {
        DCHECK(is_complete_);
        owning_group_->RemoveCache(this);
    }
    DCHECK(!owning_group_.get());
    storage_->working_set()->RemoveCache(this);
}

void AppCache::UnassociateHost(AppCacheHost* host)
{
    associated_hosts_.erase(host);
}

void AppCache::AddEntry(const GURL& url, const AppCacheEntry& entry)
{
    DCHECK(entries_.find(url) == entries_.end());
    entries_.insert(EntryMap::value_type(url, entry));
    cache_size_ += entry.response_size();
}

bool AppCache::AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry)
{
    std::pair<EntryMap::iterator, bool> ret = entries_.insert(EntryMap::value_type(url, entry));

    // Entry already exists.  Merge the types of the new and existing entries.
    if (!ret.second)
        ret.first->second.add_types(entry.types());
    else
        cache_size_ += entry.response_size(); // New entry. Add to cache size.
    return ret.second;
}

void AppCache::RemoveEntry(const GURL& url)
{
    EntryMap::iterator found = entries_.find(url);
    DCHECK(found != entries_.end());
    cache_size_ -= found->second.response_size();
    entries_.erase(found);
}

AppCacheEntry* AppCache::GetEntry(const GURL& url)
{
    EntryMap::iterator it = entries_.find(url);
    return (it != entries_.end()) ? &(it->second) : nullptr;
}

const AppCacheEntry* AppCache::GetEntryAndUrlWithResponseId(
    int64_t response_id,
    GURL* optional_url_out)
{
    for (EntryMap::const_iterator iter = entries_.begin();
         iter != entries_.end(); ++iter) {
        if (iter->second.response_id() == response_id) {
            if (optional_url_out)
                *optional_url_out = iter->first;
            return &iter->second;
        }
    }
    return nullptr;
}

AppCacheExecutableHandler* AppCache::GetExecutableHandler(int64_t response_id)
{
    HandlerMap::const_iterator found = executable_handlers_.find(response_id);
    if (found != executable_handlers_.end())
        return found->second.get();
    return nullptr;
}

AppCacheExecutableHandler* AppCache::GetOrCreateExecutableHandler(
    int64_t response_id,
    net::IOBuffer* handler_source)
{
    AppCacheExecutableHandler* handler = GetExecutableHandler(response_id);
    if (handler)
        return handler;

    GURL handler_url;
    const AppCacheEntry* entry = GetEntryAndUrlWithResponseId(
        response_id, &handler_url);
    if (!entry || !entry->IsExecutable())
        return nullptr;

    DCHECK(storage_->service()->handler_factory());
    std::unique_ptr<AppCacheExecutableHandler> own_ptr = storage_->service()->handler_factory()->CreateHandler(handler_url,
        handler_source);
    handler = own_ptr.get();
    if (!handler)
        return nullptr;
    executable_handlers_[response_id] = std::move(own_ptr);
    return handler;
}

GURL AppCache::GetNamespaceEntryUrl(const AppCacheNamespaceVector& namespaces,
    const GURL& namespace_url) const
{
    size_t count = namespaces.size();
    for (size_t i = 0; i < count; ++i) {
        if (namespaces[i].namespace_url == namespace_url)
            return namespaces[i].target_url;
    }
    NOTREACHED();
    return GURL();
}

namespace {
    bool SortNamespacesByLength(
        const AppCacheNamespace& lhs, const AppCacheNamespace& rhs)
    {
        return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length();
    }
}

void AppCache::InitializeWithManifest(AppCacheManifest* manifest)
{
    DCHECK(manifest);
    intercept_namespaces_.swap(manifest->intercept_namespaces);
    fallback_namespaces_.swap(manifest->fallback_namespaces);
    online_whitelist_namespaces_.swap(manifest->online_whitelist_namespaces);
    online_whitelist_all_ = manifest->online_whitelist_all;

    // Sort the namespaces by url string length, longest to shortest,
    // since longer matches trump when matching a url to a namespace.
    std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(),
        SortNamespacesByLength);
    std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
        SortNamespacesByLength);
}

void AppCache::InitializeWithDatabaseRecords(
    const AppCacheDatabase::CacheRecord& cache_record,
    const std::vector<AppCacheDatabase::EntryRecord>& entries,
    const std::vector<AppCacheDatabase::NamespaceRecord>& intercepts,
    const std::vector<AppCacheDatabase::NamespaceRecord>& fallbacks,
    const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists)
{
    DCHECK(cache_id_ == cache_record.cache_id);
    online_whitelist_all_ = cache_record.online_wildcard;
    update_time_ = cache_record.update_time;

    for (size_t i = 0; i < entries.size(); ++i) {
        const AppCacheDatabase::EntryRecord& entry = entries.at(i);
        AddEntry(entry.url, AppCacheEntry(entry.flags, entry.response_id, entry.response_size));
    }
    DCHECK(cache_size_ == cache_record.cache_size);

    for (size_t i = 0; i < intercepts.size(); ++i)
        intercept_namespaces_.push_back(intercepts.at(i).namespace_);

    for (size_t i = 0; i < fallbacks.size(); ++i)
        fallback_namespaces_.push_back(fallbacks.at(i).namespace_);

    // Sort the fallback namespaces by url string length, longest to shortest,
    // since longer matches trump when matching a url to a namespace.
    std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(),
        SortNamespacesByLength);
    std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
        SortNamespacesByLength);

    for (size_t i = 0; i < whitelists.size(); ++i) {
        const AppCacheDatabase::OnlineWhiteListRecord& record = whitelists.at(i);
        online_whitelist_namespaces_.push_back(
            AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE,
                record.namespace_url,
                GURL(),
                record.is_pattern));
    }
}

void AppCache::ToDatabaseRecords(
    const AppCacheGroup* group,
    AppCacheDatabase::CacheRecord* cache_record,
    std::vector<AppCacheDatabase::EntryRecord>* entries,
    std::vector<AppCacheDatabase::NamespaceRecord>* intercepts,
    std::vector<AppCacheDatabase::NamespaceRecord>* fallbacks,
    std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists)
{
    DCHECK(group && cache_record && entries && fallbacks && whitelists);
    DCHECK(entries->empty() && fallbacks->empty() && whitelists->empty());

    cache_record->cache_id = cache_id_;
    cache_record->group_id = group->group_id();
    cache_record->online_wildcard = online_whitelist_all_;
    cache_record->update_time = update_time_;
    cache_record->cache_size = 0;

    for (EntryMap::const_iterator iter = entries_.begin();
         iter != entries_.end(); ++iter) {
        entries->push_back(AppCacheDatabase::EntryRecord());
        AppCacheDatabase::EntryRecord& record = entries->back();
        record.url = iter->first;
        record.cache_id = cache_id_;
        record.flags = iter->second.types();
        record.response_id = iter->second.response_id();
        record.response_size = iter->second.response_size();
        cache_record->cache_size += record.response_size;
    }

    GURL origin = group->manifest_url().GetOrigin();

    for (size_t i = 0; i < intercept_namespaces_.size(); ++i) {
        intercepts->push_back(AppCacheDatabase::NamespaceRecord());
        AppCacheDatabase::NamespaceRecord& record = intercepts->back();
        record.cache_id = cache_id_;
        record.origin = origin;
        record.namespace_ = intercept_namespaces_[i];
    }

    for (size_t i = 0; i < fallback_namespaces_.size(); ++i) {
        fallbacks->push_back(AppCacheDatabase::NamespaceRecord());
        AppCacheDatabase::NamespaceRecord& record = fallbacks->back();
        record.cache_id = cache_id_;
        record.origin = origin;
        record.namespace_ = fallback_namespaces_[i];
    }

    for (size_t i = 0; i < online_whitelist_namespaces_.size(); ++i) {
        whitelists->push_back(AppCacheDatabase::OnlineWhiteListRecord());
        AppCacheDatabase::OnlineWhiteListRecord& record = whitelists->back();
        record.cache_id = cache_id_;
        record.namespace_url = online_whitelist_namespaces_[i].namespace_url;
        record.is_pattern = online_whitelist_namespaces_[i].is_pattern;
    }
}

bool AppCache::FindResponseForRequest(const GURL& url,
    AppCacheEntry* found_entry, GURL* found_intercept_namespace,
    AppCacheEntry* found_fallback_entry, GURL* found_fallback_namespace,
    bool* found_network_namespace)
{
    // Ignore fragments when looking up URL in the cache.
    GURL url_no_ref;
    if (url.has_ref()) {
        GURL::Replacements replacements;
        replacements.ClearRef();
        url_no_ref = url.ReplaceComponents(replacements);
    } else {
        url_no_ref = url;
    }

    // 6.6.6 Changes to the networking model

    AppCacheEntry* entry = GetEntry(url_no_ref);
    if (entry) {
        *found_entry = *entry;
        return true;
    }

    *found_network_namespace = IsInNetworkNamespace(url_no_ref);
    if (*found_network_namespace)
        return true;

    const AppCacheNamespace* intercept_namespace = FindInterceptNamespace(url_no_ref);
    if (intercept_namespace) {
        entry = GetEntry(intercept_namespace->target_url);
        DCHECK(entry);
        *found_entry = *entry;
        *found_intercept_namespace = intercept_namespace->namespace_url;
        return true;
    }

    const AppCacheNamespace* fallback_namespace = FindFallbackNamespace(url_no_ref);
    if (fallback_namespace) {
        entry = GetEntry(fallback_namespace->target_url);
        DCHECK(entry);
        *found_fallback_entry = *entry;
        *found_fallback_namespace = fallback_namespace->namespace_url;
        return true;
    }

    *found_network_namespace = online_whitelist_all_;
    return *found_network_namespace;
}

void AppCache::ToResourceInfoVector(AppCacheResourceInfoVector* infos) const
{
    DCHECK(infos && infos->empty());
    for (EntryMap::const_iterator iter = entries_.begin();
         iter != entries_.end(); ++iter) {
        infos->push_back(AppCacheResourceInfo());
        AppCacheResourceInfo& info = infos->back();
        info.url = iter->first;
        info.is_master = iter->second.IsMaster();
        info.is_manifest = iter->second.IsManifest();
        info.is_intercept = iter->second.IsIntercept();
        info.is_fallback = iter->second.IsFallback();
        info.is_foreign = iter->second.IsForeign();
        info.is_explicit = iter->second.IsExplicit();
        info.size = iter->second.response_size();
        info.response_id = iter->second.response_id();
    }
}

// static
const AppCacheNamespace* AppCache::FindNamespace(
    const AppCacheNamespaceVector& namespaces,
    const GURL& url)
{
    size_t count = namespaces.size();
    for (size_t i = 0; i < count; ++i) {
        if (namespaces[i].IsMatch(url))
            return &namespaces[i];
    }
    return nullptr;
}

} // namespace content
